home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 November: Tool Chest / Dev.CD Nov 00 TC Disk 1.toast / Sample Code / Networking / PPCToolboxKeychain / PPCToolboxKeychain.c < prev    next >
Encoding:
Text File  |  2000-10-06  |  15.1 KB  |  491 lines  |  [TEXT/CWIE]

  1. /*
  2.     File:        PPCToolboxKeychain.c
  3.  
  4.     Contains:    'osax' for creating PPC Toolbox keys in the keychain.
  5.  
  6.     Written by:    Quinn "The Eskimo!"
  7.  
  8.     Copyright:    Copyright © 2000 by Apple Computer, Inc., All Rights Reserved.
  9.  
  10.     Disclaimer:    IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
  11.                 ("Apple") in consideration of your agreement to the following terms, and your
  12.                 use, installation, modification or redistribution of this Apple software
  13.                 constitutes acceptance of these terms.  If you do not agree with these terms,
  14.                 please do not use, install, modify or redistribute this Apple software.
  15.  
  16.                 In consideration of your agreement to abide by the following terms, and subject
  17.                 to these terms, Apple grants you a personal, non-exclusive license, under Apple’s
  18.                 copyrights in this original Apple software (the "Apple Software"), to use,
  19.                 reproduce, modify and redistribute the Apple Software, with or without
  20.                 modifications, in source and/or binary forms; provided that if you redistribute
  21.                 the Apple Software in its entirety and without modifications, you must retain
  22.                 this notice and the following text and disclaimers in all such redistributions of
  23.                 the Apple Software.  Neither the name, trademarks, service marks or logos of
  24.                 Apple Computer, Inc. may be used to endorse or promote products derived from the
  25.                 Apple Software without specific prior written permission from Apple.  Except as
  26.                 expressly stated in this notice, no other rights or licenses, express or implied,
  27.                 are granted by Apple herein, including but not limited to any patent rights that
  28.                 may be infringed by your derivative works or by other works in which the Apple
  29.                 Software may be incorporated.
  30.  
  31.                 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
  32.                 WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
  33.                 WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  34.                 PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
  35.                 COMBINATION WITH YOUR PRODUCTS.
  36.  
  37.                 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
  38.                 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  39.                 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  40.                 ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
  41.                 OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
  42.                 (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
  43.                 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  44.  
  45.     Change History (most recent first):
  46.  
  47. */
  48.  
  49. /////////////////////////////////////////////////////////////////
  50.  
  51. // MIB Setup
  52.  
  53. #include "MoreSetup.h"
  54.  
  55. // Mac OS Interfaces
  56.  
  57. #include <Keychain.h>
  58. #include <CodeFragments.h>
  59. #include <StringCompare.h>
  60. #include <OpenTransportProviders.h>
  61.  
  62. // Standard C interfaces
  63.  
  64. #include <ctype.h>
  65. #include <stdlib.h>
  66.  
  67. // MIB interfaces
  68.  
  69. #include "MoreAppleEvents.h"
  70.  
  71. // Apple Event Constants
  72.  
  73. #include "PPCToolboxKeychain.h"
  74.  
  75. /////////////////////////////////////////////////////////////////
  76. #pragma mark ----- Global Data -----
  77.  
  78. static FSSpec gFragmentFSSpec;                            // location of osax file
  79.  
  80. static AEEventHandlerUPP gBlessKeyHandlerUPP = nil;        // -> BlessKeyHandlerProc
  81.  
  82. static Boolean gHaveInstalled = false;                    // have we installed our event handler
  83.  
  84. __declspec(export) UInt32 gAdditionReferenceCount = 0;    // required by AppleScript to prevent unload while busy
  85.  
  86. /////////////////////////////////////////////////////////////////
  87. #pragma mark ----- Event Handlers -----
  88.  
  89. static Boolean CheckForChars(const char **cursor, const char *templ)
  90.     // Assuming the *cursor is a pointer to an input string and
  91.     // templ is a pointer to a constant template string,
  92.     // check that *cursor starts with the characters in the template,
  93.     // return true if the template is found or false otherwise.
  94.     // Also updates *cursor as it ‘consumes’ characters.
  95.     //
  96.     // For example, if *cursor points to "eppc://foo" and
  97.     // templ points to "eppc://", this will return true
  98.     // and update *cursor to point to the the "f" in "foo".
  99. {
  100.     MoreAssertQ(cursor != nil);
  101.     MoreAssertQ(*cursor != nil);
  102.     MoreAssertQ(templ != nil);
  103.     
  104.     while (**cursor == *templ && *templ != 0) {
  105.         *cursor += 1;
  106.         templ += 1;
  107.     }
  108.     return *templ == 0;
  109. }
  110.  
  111. static Boolean CollectChars(const char **cursor, char terminator, UInt8 *buffer, ByteCount bufferSize, ByteCount *actualSize)
  112.     // Copies characters from an input string (pointed to be *cursor ) to an output buffer
  113.     // (described by buffer and bufferSize), terminating when either a) we hit a null terminator
  114.     // in *cursor, or when we hit the terminator character in the input string.  Updates
  115.     // *cursor to point to the terminating character.  Also returns, in *actualSize, the number
  116.     // of bytes that were copied to the buffer.  Converts escape sequences (%xx) into the
  117.     // appropriate character in the output buffer.
  118.     // 
  119.     // Returns true if the copy was successful, or false if it failed.  Reasons for failure
  120.     // include a) running out of space in the output buffer, b) a bogus escape sequence, or
  121.     // c) not copying any characters to the output buffer (null output is not allowed).
  122. {
  123.     Boolean result;
  124.  
  125.     MoreAssertQ(cursor  != nil);
  126.     MoreAssertQ(*cursor != nil);
  127.     MoreAssertQ(buffer  != nil);
  128.     MoreAssertQ(actualSize != nil);
  129.  
  130.     *actualSize = 0;
  131.  
  132.     result = true;
  133.     do {
  134.     
  135.         // If we’re run out of input characters, leave with result true.
  136.         
  137.         if ( **cursor == 0 || **cursor == terminator ) {
  138.             break;
  139.         }
  140.  
  141.         // We know we’re going for another output character.  If we don’t have enough
  142.         // space, fail with an error.
  143.         
  144.         if ( *actualSize == bufferSize ) {
  145.             result = false;
  146.             break;
  147.         }
  148.         
  149.         // Now look for a '%'; if we find one, interpret the digits that follow as hex.
  150.         // Otherwise just copy the character across.
  151.         
  152.         if (**cursor == '%') {
  153.             char tmpStr[3];
  154.             
  155.             *cursor += 1;        // skip '%'
  156.             
  157.             tmpStr[0] = (*cursor)[0];
  158.             tmpStr[1] = (*cursor)[1];
  159.             tmpStr[2] = 0;
  160.             
  161.             if ( ! isxdigit(tmpStr[0]) || ! isxdigit(tmpStr[1]) ) {
  162.                 result = false;
  163.                 break;
  164.             }
  165.             *cursor += 2;        // skip hex digits
  166.  
  167.             buffer[*actualSize] = (UInt8) strtoul(tmpStr, nil, 16);;
  168.         } else {
  169.             buffer[*actualSize] = (UInt8) **cursor;
  170.             *cursor += 1;
  171.         }
  172.         *actualSize += 1;
  173.  
  174.     } while (true);
  175.     
  176.     // If we didn’t copy across /any/ characters, that’s an error.
  177.  
  178.     if (result && *actualSize == 0) {
  179.         result = false;
  180.     }
  181.     
  182.     return result;
  183. }
  184.  
  185. static OSStatus ParseURLIntoGenericData(const char *url, UInt8 *genaBuffer, ByteCount *genaBufferSize)
  186.     // Parses strings of the form eppc://dns.name or eppc:/at/machine:zone
  187.     // into the appropriate 'gena' attribute format (either a packed NBPEntity
  188.     // or a LocationNameRec with locationKindSelector of ppcXTIAddrLocation).
  189. {
  190.     OSStatus err;
  191.     const char *cursor;
  192.     LocationNamePtr loc;
  193.     ByteCount actualSize;
  194.     UInt8 *outputCursor;
  195.     
  196.     MoreAssertQ(url != nil);
  197.     MoreAssertQ(genaBuffer != nil);
  198.     MoreAssertQ(genaBufferSize != nil);
  199.     
  200.     err = -1;
  201.  
  202.     cursor = url;
  203.     
  204.     if ( ! CheckForChars(&cursor, "eppc:/") ) goto parseError;
  205.     
  206.     switch (*cursor) {
  207.         case '/':
  208.             // eppc://dns.name
  209.  
  210.             // For TCP/IP, the 'gena' attribute for a PPC Toolbox key is in the
  211.             // same LocationNameRec format used when sending Apple events over TCP/IP.
  212.             // See DTS Technote 1176 for more details.
  213.             // 
  214.             // <http://developer.apple.com/technotes/tn/tn1176.html#ppctoolbox>
  215.  
  216.             cursor += 1;
  217.  
  218.             loc = (LocationNamePtr) genaBuffer;
  219.             loc->locationKindSelector  = ppcXTIAddrLocation;
  220.             loc->u.xtiType.Reserved[0] = 0;
  221.             loc->u.xtiType.Reserved[1] = 0;
  222.             loc->u.xtiType.Reserved[2] = 0;
  223.             loc->u.xtiType.xtiAddr.fAddressType = AF_DNS;
  224.  
  225.             if ( ! CollectChars(&cursor, 0, loc->u.xtiType.xtiAddr.fAddress, sizeof(loc->u.xtiType.xtiAddr.fAddress), &actualSize) ) goto parseError;
  226.             if ( *cursor != 0) goto parseError;
  227.  
  228.             loc->u.xtiType.xtiAddrLen = sizeof(PPCXTIAddressType) + actualSize;
  229.             
  230.             *genaBufferSize = loc->u.xtiType.xtiAddrLen + sizeof(loc->locationKindSelector) 
  231.                                                         + sizeof(loc->u.xtiType.Reserved) 
  232.                                                         + sizeof(loc->u.xtiType.xtiAddrLen);
  233.             break;
  234.         case 'a':
  235.             // eppc:/at/machine:zone
  236.             
  237.             // For AppleTalk, the 'gena' attribute for a PPC Toolbox key is in the
  238.             // standard NBPEntity format (ie three packed Pascal strings, each of
  239.             // 32 characters or less).
  240.             
  241.             if ( ! CheckForChars(&cursor, "at/") ) goto parseError;
  242.  
  243.             outputCursor = genaBuffer;
  244.             
  245.             if ( ! CollectChars(&cursor, ':', outputCursor + 1, 32, &actualSize) ) goto parseError;
  246.             if ( *cursor != ':') goto parseError;
  247.             cursor += 1;
  248.             
  249.             *outputCursor = actualSize;
  250.             outputCursor += *outputCursor + 1;
  251.             
  252.             BlockMove("\pPPCToolBox", outputCursor, 11);
  253.             outputCursor += 11;
  254.  
  255.             if ( ! CollectChars(&cursor, '0', outputCursor + 1, 32, &actualSize) ) goto parseError;
  256.             if ( *cursor != 0) goto parseError;
  257.  
  258.             *outputCursor = actualSize;
  259.             outputCursor += *outputCursor + 1;
  260.             
  261.             *genaBufferSize = outputCursor - genaBuffer;
  262.             break;
  263.         default:
  264.             goto parseError;
  265.     }
  266.     
  267.     err = noErr;
  268.  
  269. parseError:
  270.  
  271.     return err;
  272. }
  273.  
  274. static pascal OSErr BlessKeyHandlerProc(const AppleEvent *theAppleEvent, AppleEvent *reply, UInt32 handlerRefcon)
  275.     // The Apple event handler installed by our osax's fragment initialisation routine.
  276.     // This handler extracts the event parameters ("name" and "addr"), looks up the 
  277.     // generic key by name in the default keychain, then parses the "addr" URL into
  278.     // a 'gena' key attribute and stores that attribute in the key.
  279. {
  280.     OSStatus     err;
  281.     OSStatus     junk;
  282.     AEDesc         nameDesc;
  283.     AEDesc         addrDesc;
  284.     Str255         name;
  285.     char         addr[256];
  286.     KCItemRef     foundItem;
  287.     UInt8        genaBuffer[256];
  288.     ByteCount    genaBufferSize;
  289.     
  290.     MoreAssertQ(theAppleEvent != nil);
  291.     MoreAssertQ(reply != nil);
  292.     #if MORE_DEBUG
  293.         MoreAssertQ(handlerRefcon == 0);
  294.     #else
  295.         #pragma unused(handlerRefcon)
  296.     #endif
  297.  
  298.     gAdditionReferenceCount += 1;        // tell AppleScript we’re busy
  299.         
  300.     MoreAENullDesc(&nameDesc);
  301.     MoreAENullDesc(&addrDesc);
  302.     foundItem = nil;
  303.     
  304.     err = noErr;
  305.     if ( !(KeychainManagerAvailable()) ) {
  306.         err = unimpErr;
  307.     }
  308.     err = AEGetParamDesc(theAppleEvent, keyAEName, typeText, &nameDesc);
  309.     if (err == noErr) {
  310.         err = MoreAEGetPStringFromDescriptor(&nameDesc, name);
  311.     }
  312.     if (err == noErr) {
  313.         err = AEGetParamDesc(theAppleEvent, kAEISClientAddress, typeText, &addrDesc);
  314.     }
  315.     if (err == noErr) {
  316.         err = MoreAEGetCStringFromDescriptor(&addrDesc, addr);
  317.     }
  318.     if (err == noErr) {
  319.         err = MoreAEGotRequiredParams(theAppleEvent);
  320.     }
  321.     if (err == noErr) {
  322.         err = KCFindGenericPassword(name, nil, 0, nil, 0, &foundItem);
  323.     }
  324.     if (err == noErr) {
  325.         genaBufferSize = sizeof(genaBuffer);
  326.         err = ParseURLIntoGenericData(addr, genaBuffer, &genaBufferSize);
  327.     }
  328.     if (err == noErr) {
  329.         KCAttribute attr;
  330.  
  331.         attr.tag     = kGenericKCItemAttr;
  332.         attr.length = genaBufferSize;
  333.         attr.data     = genaBuffer;
  334.         err = KCSetAttribute(foundItem, &attr);
  335.     }
  336.     if (err == noErr) {
  337.         err = KCUpdateItem(foundItem);
  338.     }
  339.     
  340.     if (reply->dataHandle != nil) {
  341.         // Need to put the an error string into the keyErrorString parameter of
  342.         // reply here.  Only necessary for errors that AppleScript doesn’t already 
  343.         // know about, such as the unimpErr we generate if Keychain isn't available.
  344.         // This code is left as an exercise for the reader.
  345.     }
  346.     
  347.     if (foundItem != nil) {
  348.         junk = KCReleaseItem(&foundItem);
  349.         MoreAssertQ(junk == noErr);
  350.     }
  351.     MoreAEDisposeDesc(&nameDesc);
  352.     MoreAEDisposeDesc(&addrDesc);
  353.  
  354.     gAdditionReferenceCount -= 1;        // tell AppleScript we’re no longer busy
  355.         
  356.     return err;
  357. }
  358.  
  359. /////////////////////////////////////////////////////////////////
  360. #pragma mark ----- Init/Term -----
  361.  
  362. extern OSErr FragmentInit(const CFragInitBlock *initBlock);
  363. extern void  FragmentTerm(void);
  364.  
  365. extern OSErr FragmentInit(const CFragInitBlock *initBlock)
  366.     // This routine is the native osax's fragment initialization
  367.     // routine.  It’s responsible for installing our event handler.
  368. {
  369.     OSStatus err;
  370.  
  371.     #if MORE_DEBUG
  372.         DebugStr("\pFragmentInit");
  373.     #endif
  374.     
  375.     MoreAssertQ(gAdditionReferenceCount == 0);    // Either the data initialization failed, or something else bad has happened.
  376.  
  377.     // We latch the FSSpec of the 'osax' file in case we need to access
  378.     // our resource fork.  We currently don’t use this info, but a future
  379.     // extensions might use it (to get better error numbers, for example).
  380.     
  381.     MoreAssertQ(initBlock->fragLocator.where == kDataForkCFragLocator);
  382.     gFragmentFSSpec = *(initBlock->fragLocator.u.onDisk.fileSpec);
  383.     
  384.     // Install the handler.
  385.     
  386.     err = noErr;
  387.     gBlessKeyHandlerUPP = NewAEEventHandlerUPP(BlessKeyHandlerProc);
  388.     if (gBlessKeyHandlerUPP == nil) {
  389.         err = memFullErr;
  390.     }
  391.     if (err == noErr) {
  392.         err = AEInstallEventHandler(kBlessKeyClassID, kBlessKeyEventID, gBlessKeyHandlerUPP, 0, true);
  393.         gHaveInstalled = (err == noErr);
  394.     }
  395.     
  396.     // Clean up.
  397.     
  398.     if (err != noErr) {
  399.         FragmentTerm();
  400.     }
  401.     
  402.     return err;
  403. }
  404.  
  405. extern void  FragmentTerm(void)
  406.     // This routine is the native osax's fragment termination
  407.     // routine.  It’s responsible for tearing down any initialization
  408.     // done by FragmentInit.
  409. {
  410.     OSStatus junk;
  411.     
  412.     #if MORE_DEBUG
  413.         DebugStr("\pFragmentTerm");
  414.     #endif
  415.     
  416.     MoreAssertQ(gAdditionReferenceCount == 0);    // Why are we being unloaded if we’re busy?
  417.     
  418.     if (gHaveInstalled) {
  419.         MoreAssertQ(gBlessKeyHandlerUPP != nil);
  420.         junk = AERemoveEventHandler(kBlessKeyClassID, kBlessKeyEventID, gBlessKeyHandlerUPP, true);
  421.         MoreAssertQ(junk == noErr);
  422.         gHaveInstalled = false;
  423.     }
  424.     if (gBlessKeyHandlerUPP != nil) {
  425.         DisposeAEEventHandlerUPP(gBlessKeyHandlerUPP);
  426.         gBlessKeyHandlerUPP = nil;
  427.     }
  428. }
  429.  
  430. #ifdef PPCTOOLBOX_KEYCHAIN_TESTBED
  431.  
  432. // The code inside this conditional is only used by the Testbed
  433. // target to create a testbed application that let’s us debug
  434. // the core of the 'osax' without having to mess around debugging
  435. // code resources.
  436.  
  437. #include <stdio.h>
  438.  
  439. static void TestParseURL(const char *url)
  440. {
  441.     OSStatus err;
  442.     UInt8 genaBuf[256];
  443.     ByteCount genaBufSize;
  444.     ByteCount thisByteIndex;
  445.  
  446.     genaBufSize = sizeof(genaBuf);
  447.     err = ParseURLIntoGenericData(url, genaBuf, &genaBufSize);
  448.     if (err == noErr) {
  449.         printf("ParseURLIntoGenericData(%s) = \n", url);
  450.         for (thisByteIndex = 0; thisByteIndex < genaBufSize; thisByteIndex++) {
  451.             printf("%02x ", genaBuf[thisByteIndex]);
  452.         }
  453.         printf("\n");
  454.     } else {
  455.         printf("••• ParseURLIntoGenericData(%s), err = %ld\n", url, err);
  456.     }
  457. }
  458.  
  459. extern void main(void)
  460. {
  461.     printf("PPCToolboxKeychain Testbed!\n");
  462.  
  463.     // √ means test should succeed
  464.     // x means test should fail
  465.     
  466.     TestParseURL("eppc://guy-smiley.apple.com");            // √
  467.     TestParseURL("eppc:/at/Guy Smiley:*");                    // √
  468.     TestParseURL("foo");                                    // x
  469.     TestParseURL("eppc:");                                    // x
  470.     TestParseURL("eppc:/");                                    // x
  471.     TestParseURL("eppc://");                                // x
  472.     TestParseURL("eppc:/bob/");                                // x
  473.     TestParseURL("eppc:/ab/");                                // x
  474.     TestParseURL("eppc:/at/");                                // x
  475.     TestParseURL("eppc:/at/machine");                        // x
  476.     TestParseURL("eppc:/at/machine:");                        // x
  477.     TestParseURL("eppc:/at/:");                                // x
  478.     TestParseURL("eppc:/at/:zone");                            // x
  479.     TestParseURL("eppc:/at/machine:zone");                    // √
  480.     TestParseURL("eppc:/at/machine%3Awith%3Acolons:zone");    // √
  481.     TestParseURL("eppc:/at/01234567890123456789012345678901:zone");            // √
  482.     TestParseURL("eppc:/at/012345678901234567890123456789012:zone");        // x
  483.     TestParseURL("eppc://%");                                // x
  484.     TestParseURL("eppc://%x");                                // x
  485.     TestParseURL("eppc://%xy");                                // x
  486.     
  487.     printf("Done.  Press command-Q to Quit.\n");
  488. }
  489.  
  490. #endif
  491.